package v1

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"
	"zhejianglab/gin-apiserver/model"
	"zhejianglab/gin-apiserver/model/request"
	"zhejianglab/gin-apiserver/pkg/grpc"
	pb "zhejianglab/gin-apiserver/proto"

	"github.com/astaxie/beego/validation"
	"github.com/gin-gonic/gin"
)

// AddNewJob create new job
// @Produce  json
// @Tags job
// @Param AddNewJob body request.JobInfo true "request.JobInfo"
// @Success 201 {object} gin.H "{"code":201,"msg":"AddNewJob successfully"}"
// @Failure 409 {object} gin.H "{"code":500,"msg":"AddNewJob: Job name is exist"}"
// @Failure 500 {object} gin.H "{"code":500,"msg":"AddNewJob: Job name is invalid"}"
// @Router /api/v1/job/new [post]
func AddNewJob(c *gin.Context) {
	var err error

	var Job request.JobInfo
	err = c.ShouldBindJSON(&Job)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"status": http.StatusInternalServerError, "message": err.Error(), "Error Message ": err})
		return
	}
	if Job.VideoID != 0 && Job.DatasetID != 0 || Job.VideoID == 0 && Job.DatasetID == 0 {
		c.JSON(http.StatusInternalServerError, gin.H{"status": http.StatusInternalServerError, "message": "video id and dataset id can not be exist in time", "Error Message ": ""})
		return
	}
	if Job.VideoID != 0 && Job.DataType == 1 || Job.DatasetID != 0 && Job.DataType == 0 {
		c.JSON(http.StatusInternalServerError, gin.H{"status": http.StatusInternalServerError, "message": "data type is not correct", "Error Message ": ""})
		return
	}
	valid := validation.Validation{}

	// 校验jobName、AlgorithmID的有效性
	valid.Required(Job.Name, "name").Message("job名称不能为空")
	if valid.HasErrors() {
		err = errors.New("AddNewJob: Job name is invalid")
		c.JSON(http.StatusInternalServerError, gin.H{"status": http.StatusInternalServerError, "message": err.Error()})
		return
	}
	valid.Required(Job.AlgorithmID, "algorithm_id").Message("AlgorithmID不能为空")
	if valid.HasErrors() {
		err = errors.New("AddNewJob: AlgorithmID is invalid")
		c.JSON(http.StatusInternalServerError, gin.H{"status": http.StatusInternalServerError, "message": err.Error()})
		return
	}
	if Job.Type != 3 && Job.SearchImagePath != "" {
		c.JSON(http.StatusInternalServerError, gin.H{"status": http.StatusInternalServerError, "message": "非ReID算法 SearchImagePath 为空"})
		return
	}
	jobExist, jobDBInfo := model.ExistJob(Job.AlgorithmID, Job.VideoID, Job.DatasetID)
	if jobExist && jobDBInfo.SearchImagePath == Job.SearchImagePath {
		//ReID 任务如果搜索图片遍历，当成新任务处理
		if jobDBInfo.Progress == 0 {
			err = PutJob(jobDBInfo)
			if err != nil {
				c.JSON(http.StatusInternalServerError, gin.H{"status": http.StatusInternalServerError, "message": err.Error()})
				return
			}
			c.JSON(http.StatusCreated, gin.H{"status": http.StatusCreated, "message": "AddNewJob successfully!", "data": jobDBInfo})
			return
		}
		c.JSON(http.StatusConflict, gin.H{"status": http.StatusConflict, "message": "the job is exist ", "data": jobDBInfo})
		return
	}

	jobInfo := model.JobInfo{
		Name:            Job.Name,
		Type:            Job.Type,
		AlgorithmID:     Job.AlgorithmID,
		BeginTime:       Job.BeginTime,
		EndTime:         Job.EndTime,
		Progress:        Job.Progress,
		DataType:        Job.DataType,
		VideoID:         Job.VideoID,
		DatasetID:       Job.DatasetID,
		ResultPath:      Job.ResultPath,
		CreatedTime:     Job.CreatedTime,
		FinishedTime:    Job.FinishedTime,
		Status:          Job.Status,
		ErrorCode:       0,
		ErrorMsg:        Job.ErrorMsg,
		UserID:          Job.UserID,
		SearchImagePath: Job.SearchImagePath,
	}

	data := model.AddNewJob(&jobInfo)
	job, ok := data.(*model.JobInfo)
	if !ok {
		c.JSON(http.StatusInternalServerError, gin.H{"status": http.StatusInternalServerError, "message": "AddNewJob failed!"})
		return
	}
	// put job into queue
	err = PutJob(jobInfo)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"status": http.StatusInternalServerError, "message": err.Error()})
		return
	}
	c.JSON(http.StatusCreated, gin.H{"status": http.StatusCreated, "message": "AddNewJob successfully!", "data": job})
}

func PutJob(job model.JobInfo) error {
	strAlgorithmID := strconv.Itoa(int(job.AlgorithmID))
	data, err := model.GetAlgo(strAlgorithmID)
	if err != nil {
		err = errors.New("the algorithm is can not find in db" + err.Error())
		return err
	}
	dataPath := ""
	if job.DatasetID != 0 {
		dataset, err := model.GetDataset(strconv.Itoa(int(job.DatasetID)))
		if err != nil {
			return err
		}
		dataPath = dataset.FilePath
	} else if job.VideoID != 0 {
		video, err := model.GetVideo(strconv.Itoa(int(job.VideoID)))
		if err != nil {
			return err
		}
		dataPath = video.FileKey
	}
	elemt := pb.Message{
		RequestType: pb.RequestType_REQUESTTYPE_ACK,
		ErrorCode:   pb.ErrorCode_ERRORCODE_SUCCESS,
		MessageType: pb.MesageType_MESSAGETYPE_CREATE_JOB,
		JobType:     pb.JobType(job.Type),
		AlgorithmJob: &pb.AlgorithmJob{
			JobId:       int64(job.ID),
			DataType:    pb.DataType(job.DataType),
			ProcessPath: dataPath,
			AlgorithmInfo: &pb.AlgorithmInfo{
				AlgorithmId: job.AlgorithmID,
				LibraryPath: data.AlgorithmPath,
				ModelPath:   data.ModelPath,
				ConfigPath:  data.ConfigPath,
			},
			SearchImagePath: job.SearchImagePath,
		},
		AlgorithmResult: &pb.AlgorithmResult{
			JobId:            int64(job.ID),
			ResultPath:       "",
			ResultStatistics: "",
		},
	}

	grpc.AllJobChan <- elemt

	// switch job.Type {
	// case 0:
	// 	grpc.SuperResolutionJobChan <- elemt
	// case 1:
	// 	grpc.ObjectDetectionJobChan <- elemt
	// case 2:
	// 	grpc.ObjectTrackJobChan <- elemt
	// case 3:
	// 	grpc.ReIDJobChan <- elemt
	// case 4:
	// 	grpc.ActionRecognitionJobChan <- elemt
	// case 5:
	// 	grpc.AllJobChan <- elemt
	// default:
	// 	fmt.Println("job type is error")
	// }

	fmt.Printf("put element in %d JobQueue,job id is %d,algorithm id is %d,dataset id is %d ,video id is %d ,dataType is %d \n", job.Type, job.ID, job.AlgorithmID, job.DatasetID, job.VideoID, job.DataType)
	return nil
}

// GetJobByUserID get Job info from db by UserID
// @Produce  json
// @Tags job
// @Param userID path int true "userID"
// @Success 200 {object} gin.H "{"code":200,"msg":"GetJob successfully"}"
// @Failure 500 {object} gin.H "{"code":500,"msg":"GetJob failed!"}"
// @Router /api/v1/job/user/{userID} [get]
func GetJobByUserID(c *gin.Context) {
	userID := c.Param("id")

	if userID != "" {
		data, err := model.GetJobByUserID(userID)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"status": http.StatusInternalServerError, "message": err.Error()})
			return
		}
		c.JSON(http.StatusOK, gin.H{"status": http.StatusOK, "message": "GetJob successfully!", "data": data})
	} else {
		data, err := model.GetAllJobs()
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"status": http.StatusInternalServerError, "message": err.Error()})
			return
		}
		c.JSON(http.StatusOK, gin.H{"status": http.StatusOK, "message": "GetJob successfully!", "data": data})
	}

}

// GetResult get result info from db by jobID
// @Produce  json
// @Tags job
// @Param userID path int true "userID"
// @Success 200 {object} gin.H "{"code":200,"msg":"GetJob successfully"}"
// @Failure 500 {object} gin.H "{"code":500,"msg":"GetJob failed!"}"
// @Router /api/v1/job/result/{jobID} [get]
func GetResult(c *gin.Context) {
	jobID := c.Param("id")

	if jobID != "" {
		data, err := model.GetJob(jobID)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"status": http.StatusInternalServerError, "message": err.Error()})
			return
		}
		var result request.Result
		result.ResultStatistics = data.ResultStatistics
		result.AlgorithmID = data.AlgorithmID
		result.JobID, err = strconv.Atoi(jobID)
		result.Type = data.Type
		result.DataType = data.DataType
		if data.ResultPath == "" {
			c.JSON(http.StatusInternalServerError, gin.H{"status": http.StatusInternalServerError, "message": "result path is empty"})
			return
		}
		files, err := ioutil.ReadDir(data.ResultPath)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"status": http.StatusInternalServerError, "message": err.Error()})
			return
		}

		for _, f := range files {
			image := request.Image{ImagePath: data.ResultPath + "/" + f.Name()}
			result.Image = append(result.Image, image)
		}

		c.JSON(http.StatusOK, gin.H{"status": http.StatusOK, "message": "GetResult successfully!", "data": result})
	} else {

		c.JSON(http.StatusInternalServerError, gin.H{"status": http.StatusInternalServerError, "message": "jobID is necessary"})
		return
	}

}

// ReStartJob restart job
// @Produce  json
// @Tags job
// @Param id path int true "ID"
// @Success 200 {object} gin.H "{"code":200,"msg":"GetJob successfully"}"
// @Failure 500 {object} gin.H "{"code":500,"msg":"GetJob failed!"}"
// @Router /api/v1/job/restart/{ID} [get]
func ReStartJob(c *gin.Context) {
	ID := c.Param("id")
	fmt.Println("restart job", ID)
	c.JSON(http.StatusOK, gin.H{"status": http.StatusOK, "message": "ReStartJob successfully!", "data": ID})

}

// GetJob get Job info from db
// @Produce  json
// @Tags job
// @Param id path int true "ID"
// @Success 200 {object} gin.H "{"code":200,"msg":"GetJob successfully"}"
// @Failure 500 {object} gin.H "{"code":500,"msg":"GetJob failed!"}"
// @Router /api/v1/job [get]
func GetJob(c *gin.Context) {
	algorithm_id := c.Query("algorithm_id")
	id := c.Query("id")
	video_id := c.Query("video_id")
	dataset_id := c.Query("dataset_id")
	if algorithm_id == "" && video_id == "" && id == "" && dataset_id == "" {
		data, err := model.GetAllJobs()
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"status": http.StatusInternalServerError, "message": err.Error()})
			return
		}
		c.JSON(http.StatusOK, gin.H{"status": http.StatusOK, "message": "GetJob successfully!", "data": data})
		return
	}
	if video_id != "" && dataset_id != "" {
		err := errors.New("videoID and DatasetID sholud not exist in same time")
		c.JSON(http.StatusInternalServerError, gin.H{"status": http.StatusInternalServerError, "message": err.Error()})
		return
	}
	if id != "" {
		data, err := model.GetJob(id)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"status": http.StatusInternalServerError, "message": err.Error()})
			return
		}
		c.JSON(http.StatusOK, gin.H{"status": http.StatusOK, "message": "GetJob By ID successfully!", "data": data})
		return
	} else {
		if video_id != "" {
			data, err := model.GetJobWithVideoOrAlgorithm(video_id, algorithm_id)
			if err != nil {
				c.JSON(http.StatusInternalServerError, gin.H{"status": http.StatusInternalServerError, "message": err.Error()})
				return
			}
			c.JSON(http.StatusOK, gin.H{"status": http.StatusOK, "message": "GetJob successfully!", "data": data})
			return
		} else if dataset_id != "" {
			data, err := model.GetJobWithDatasetOrAlgorithm(dataset_id, algorithm_id)
			if err != nil {
				c.JSON(http.StatusInternalServerError, gin.H{"status": http.StatusInternalServerError, "message": err.Error()})
				return
			}
			c.JSON(http.StatusOK, gin.H{"status": http.StatusOK, "message": "GetJob successfully!", "data": data})
			return
		}

	}

}

// UpdateJob update new job
// @Produce  json
// @Tags job
// @Param JobInfo body request.JobInfo true "request.JobInfo"
// @Success 201 {object} gin.H "{"code":201,"msg":"UpdateJob successfully"}"
// @Failure 409 {object} gin.H "{"code":500,"msg":"UpdateJob: Job name is exist"}"
// @Failure 500 {object} gin.H "{"code":500,"msg":"UpdateJob: Job name is invalid"}"
// @Router /api/v1/job/{id} [put]
func UpdateJob(c *gin.Context) {
	var err error
	data := make(map[string]interface{})
	id, err := strconv.Atoi(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"status": http.StatusInternalServerError, "message": err.Error()})
		return
	}
	if !model.ExistJobByID(id) {
		c.JSON(http.StatusInternalServerError, gin.H{"status": http.StatusInternalServerError, "message": "Job id is not exists failed!"})
		return
	}

	var Job request.JobInfo

	err = c.ShouldBindJSON(&Job)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"status": http.StatusInternalServerError, "message": "UpdateJob failed!" + err.Error()})
		return
	}
	if Job.Progress != 0 {
		data["progress"] = Job.Progress
	}
	if Job.UserID != 0 {
		data["user_id"] = Job.UserID
	}

	model.UpdateJob(id, data)
	c.JSON(http.StatusOK, gin.H{"status": http.StatusOK, "message": "UpdateJob successfully!", "data": data})
	return

}

// DeleteJob delete case info from db
// @Produce  json
// @Tags job
// @Param id path int true "ID"
// @Success 200 {object} gin.H "{"code":200,"msg":"DeleteJob successfully"}"
// @Failure 500 {object} gin.H "{"code":500,"msg":"DeleteJob failed!"}"
// @Failure 500 {object} gin.H "{"code":500,"msg":"id is invalid!"}"
// @Router /api/v1/job/{id} [delete]
func DeleteJob(c *gin.Context) {
	id, err := strconv.Atoi(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"status": http.StatusInternalServerError, "message": err.Error()})
		return
	}

	valid := validation.Validation{}
	valid.Min(id, 1, "id").Message("ID必须大于0")

	if !valid.HasErrors() {
		if model.ExistJobByID(id) {
			model.DeleteJob(id)
			c.JSON(http.StatusOK, gin.H{"status": http.StatusOK, "message": "DeleteJob successfully!"})
		} else {
			c.JSON(http.StatusInternalServerError, gin.H{"status": http.StatusInternalServerError, "message": "DeleteJob failed!"})
			return
		}
	} else {
		c.JSON(http.StatusInternalServerError, gin.H{"status": http.StatusInternalServerError, "message": "id is invalid!"})
		return
	}
}
